嗨大家好,這是JS Design Pattern第二天,再次說明這系列主題都是從一本叫做 "JavaScript設計模式與開發實踐" 這本書範例練習來的,然後原本要一起組隊的快樂夥伴第一天就自爆了https://ithelp.ithome.com.tw/articles/10201535
不過別擔心,我將繼續完成他的遺志
策略模式定義:定義一系列演算法,把它們封裝起來,並使它們可以相互替換
當我們要達到某個目的,途中會使用各種方法,這個模式是可以更彈性、更方便的替換這些方法的一種模式,今天會舉兩個例子來說明。
首先第一個例子:現在要寫一個公司依考績來發獎金的程式,考績越好發的獎金(底薪xN個月)越高。
var calcBonus = function(performance, salary) {
if (performance === 'S') {
return salary * 4;
}
if (performance === 'A') {
return salary * 3;
}
if (performance === 'B') {
return salary * 2;
}
};
console.log(calcBonus('A', 100));
好就這樣寫完了,但是有些問題:
1.太多if-else述句
2.違反開放-封閉原則:若要新增一個等級或是修改規則就要深入calcBonus內部實作
所以我們來使用策略模式來重構程式碼,原則就是將不變的部分和變化的部分隔開
在這裡獎金的算法是會變的,而使用方式是不會變的,根據定義"將一系列演算法,把它們封裝起來,並使它們可以相互替換",我們可以將獎金的演算法利用JS的物件特性封裝起來,再放到相同的使用方式中:
//演算法
var strategies = {
'S': function(salary) {
return salary * 4;
},
'A': function(salary) {
return salary * 3;
},
'B': function(salary) {
return salary * 2;
}
};
//使用它
var calcBonus = function(performance, salary) {
return strategies[performance](salary);
};
console.log(calcBonus('S', 100));
這樣就完成策略模式了。
實際上我們很常會使用到這類模式,再來舉一個前端頁面的例子,很常使用的表單驗證:驗證表單輸入名稱、密碼和手機號碼。
var $form = getForm();
$form.submit(onSubmit);
function onSubmit() {
var form = this;
if (form.userName.value === '') {
alert('userName 不能為空');
return false;
}
if (form.password.value.length < 6) {
alert('密碼長度少於6位');
return false;
}
if (!/^[09]{2}[0-9]{8}$/.test(form.phoneNumber.value)) {
alert('手機號碼格式不對');
return false;
}
}
//製作表單畫面(可以忽略)
function getForm() {
var $form = $('<form>').appendTo('body').attr('method', 'post');
createLine('使用者名稱:', 'userName').appendTo($form);
createLine('密碼:', 'password').appendTo($form);
createLine('手機號碼:', 'phoneNumber').appendTo($form);
createButton().appendTo($form);
return $form;
function createButton() {
return $('<button>').text('submit');
}
function createLine(text, name) {
var $label = $('<span>').text(text);
var $input = $('<input>').attr('name', name);
return $('<div>').append($label).append($input);
}
}
上面這應該是最簡單直接的寫法,一樣會有些缺點:
1.if else結構太多。
2.若檢驗的內容須改變,就必須深入onSubmit中去修改。
3.重用性差,表單常常不只一個,每個都要判斷的話就可能會一直複製貼上。
所以,我們再利用策略模式特性來重構一下:
首先一樣先封裝策略
var strategies = {
inNotEmpty: function(val, errorMsg) {
if (val === '') {
return errorMsg;
}
},
minLength: function(val, length, errorMsg) {
if (val.length < length) {
return errorMsg;
}
},
isMobileNumber: function(val, errorMsg) {
if (!/^[09]{2}[0-9]{8}$/.test(val)) {
return errorMsg;
}
}
};
再來做一個這種表單用的驗證器(myFormValidator),注意myFormValidator裡面會建立一個Validator物件,這個Validator物件會記住你這張表單要檢查的規則要用到哪些檢查邏輯(strategies裡面的那些),以及錯誤的警告要顯示什麼
function myFormValidator(form) {
var validator = new Validator();
validator.add(form.userName.value, [{
strategy: 'inNotEmpty',
errorMsg: '使用者名稱不為空'
}, {
strategy: 'minLength:6',
errorMsg: '使用者名稱位數不得少於6'
}]);
validator.add(form.password.value, [{
strategy: 'minLength:6',
errorMsg: '密碼位數不得少於6'
}]);
validator.add(form.phoneNumber.value, [{
strategy: 'isMobileNumber',
errorMsg: '手機號碼錯誤'
}]);
var errorMsg = validator.start();
return errorMsg;
}
再來我們就要來實作Validator function
var Validator = function() {
this.cache = [];
};
Validator.prototype.add = function(item, rules) {
var self = this;
rules.forEach(function(rule) {
var strategySet = rule.strategy.split(':');
self.cache.push(function() {
var strategyName = strategySet.shift();
strategySet.unshift(item);
strategySet.push(rule.errorMsg);
return strategies[strategyName].apply(self, strategySet);
});
});
};
Validator.prototype.start = function() {
var errorMsg;
this.cache.some(function(validatorFunc) {
errorMsg = validatorFunc();
if (errorMsg) {
return true;
}
});
return errorMsg;
};
簡單來說就是將物件
{
strategy: 'minLength:6',
errorMsg: '密碼位數不得少於6'
}
所指定的策略名稱和錯誤顯示訊息丟給strategies物件來得到所封裝的function,再將此function記起來(丟入cache陣列中),真的檢查的時候直接執行即可。
接下來只要將你做好的myFormValidator綁到前端物件上就完成啦
var $form = getForm();
$form.submit(onSubmit);
function onSubmit() {
var form = this;
var errorMsg = myFormValidator(form);
if (errorMsg) {
alert(errorMsg);
return false;
}
}
最後附上完整CODE:
var $form = getForm();
$form.submit(onSubmit);
function onSubmit() {
var form = this;
var errorMsg = myFormValidator(form);
if (errorMsg) {
alert(errorMsg);
return false;
}
}
var strategies = {
inNotEmpty: function(val, errorMsg) {
if (val === '') {
return errorMsg;
}
},
minLength: function(val, length, errorMsg) {
if (val.length < length) {
return errorMsg;
}
},
isMobileNumber: function(val, errorMsg) {
if (!/^[09]{2}[0-9]{8}$/.test(val)) {
return errorMsg;
}
}
};
function myFormValidator(form) {
var validator = new Validator();
validator.add(form.userName.value, [{
strategy: 'inNotEmpty',
errorMsg: '使用者名稱不為空'
}, {
strategy: 'minLength:6',
errorMsg: '使用者名稱位數不得少於6'
}]);
validator.add(form.password.value, [{
strategy: 'minLength:6',
errorMsg: '密碼位數不得少於6'
}]);
validator.add(form.phoneNumber.value, [{
strategy: 'isMobileNumber',
errorMsg: '手機號碼錯誤'
}]);
var errorMsg = validator.start();
return errorMsg;
}
var Validator = function() {
this.cache = [];
};
Validator.prototype.add = function(item, rules) {
var self = this;
rules.forEach(function(rule) {
var strategySet = rule.strategy.split(':');
self.cache.push(function() {
var strategyName = strategySet.shift();
strategySet.unshift(item);
strategySet.push(rule.errorMsg);
return strategies[strategyName].apply(self, strategySet);
});
});
};
Validator.prototype.start = function() {
var errorMsg;
this.cache.some(function(validatorFunc) {
errorMsg = validatorFunc();
if (errorMsg) {
return true;
}
});
return errorMsg;
};
這樣寫的好處:
1.避免過多重複代碼,若類似的表單要用到這三個規則的直接套用myFormValidator就好。
2.組合彈性高,若有不同規則組合的表單也可以像製作myFormValidator一樣做一個新的檢查規則。
3.易於擴展,若有新的演算法的話只要新增在strategies裡面即可。
好以上就是策略模式!!
https://www.youtube.com/watch?v=IkG_KuMpQRM
最近看到的「水球潘」大大的教學,教得超棒的
配合書本、大大的文章一起研讀